﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.IO;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.IO;
using Beaker.OpenCube.Textures;
using Beaker.Services.FileSystem;
using Newtonsoft.Json;

#endregion

namespace Beaker.OpenCube.Resources
{
	/// <summary>
	/// Reader for reading texture pack files
	/// </summary>
	public sealed class ResourcePackReader
	{
		private readonly IImageCache _imageCache;
		private readonly IFolder _folder;
		private readonly IFolder _mainFolder;
		private readonly IFolder _fallbackFolder;


		public ResourcePackReader( IFolder folder )
			: this( folder, null, null )
		{
		}

		public ResourcePackReader( IFolder folder, IImageCache imageCache )
			: this( folder, null, imageCache )
		{
		}

		public ResourcePackReader( IFolder folder, IFolder fallbackFolder )
			: this( folder, fallbackFolder, null )
		{
		}

		public ResourcePackReader( IFolder folder, IFolder fallbackFolder, IImageCache imageCache )
		{
			Contract.Requires<ArgumentNullException>( folder != null );

			_mainFolder = folder;
			_fallbackFolder = fallbackFolder;
			_folder = fallbackFolder != null ? folder.Fallback( fallbackFolder ) : _mainFolder;
			_imageCache = imageCache;
		}

		private IFolder _assetsFolder;
		private IFolder _minecraftAssetsFolder;
		private IFolder _texturesFolder;
		private IFolder _blockTexturesFolder;

		private IFolder AssetsFolder
		{
			get
			{
				if( _assetsFolder == null )
					_assetsFolder = _folder.GetFolder( "assets" );
				return _assetsFolder;
			}
		}

		private IFolder MinecraftAssetsFolder
		{
			get { return GetFolder( AssetsFolder, ref _minecraftAssetsFolder, "minecraft" ); }
		}

		private IFolder TexturesFolder
		{
			get { return GetFolder( MinecraftAssetsFolder, ref _texturesFolder, "textures" ); }
		}

		private IFolder BlockTexturesFolder
		{
			get { return GetFolder( TexturesFolder, ref _blockTexturesFolder, "blocks" ); }
		}

		private IFolder GetFolder( IFolder parentFolder, ref IFolder backing, string name )
		{
			if( backing == null )
			{
				if( parentFolder != null )
					backing = parentFolder.GetFolder( name );
			}

			return backing;
		}


		/// <summary>
		/// Gets the metadata for this resource-pack
		/// </summary>
		/// <returns>The metadata for the resource-pack</returns>
		public ResourcePackMetadata ReadPackMetadata()
		{
			// metadata should come from main resource pack, not a fallback, so use _mainFolder
			return Metadata.Metadata.Read<ResourcePackMetadata>( _mainFolder.GetFile( "pack.mcmeta" ) );
		}

		/// <summary>
		/// Gets the image for the resource pack
		/// </summary>
		/// <returns></returns>
		public Bitmap ReadPackImage()
		{
			// pack image should come from main resource pack, not a fallback, so use _mainFolder
			return GetOrReadImage( _mainFolder.GetFile( "pack.png" ) );
		}

		/// <summary>
		/// Reads the texture for the given block type
		/// </summary>
		/// <param name="block">Type of the block to read the texture for.</param>
		/// <returns>
		/// The texture for the requested block or <c>null</c> when it could not be read.
		/// </returns>
		public BlockTexture ReadBlockTexture( Block block )
		{
			// try get the name for the block
			IReadOnlyCollection<Tuple<BlockTextureFlags, string>> mappings = BlockTextureMapping.GetTextureMapping( block );
			if( mappings == null )
				return null;

			var textures = new List<Tuple<BlockTextureFlags, Texture>>();
			foreach( Tuple<BlockTextureFlags, string> mapping in mappings )
			{
				Texture texture = ReadTexture( mapping.Item2 );
				if( texture != null )
					textures.Add( Tuple.Create( mapping.Item1, texture ) );
			}

			// no textures found, return null
			if( textures.Count == 0 )
				return null;

			return new BlockTexture( block, textures );
		}

		/// <summary>
		/// Reads the texture with the given name.
		/// </summary>
		/// <param name="name">The name of the texture to read.</param>
		/// <returns>The texture read or <c>null</c> when it could not be loaded.</returns>
		private Texture ReadTexture( string name )
		{
			Contract.Requires<ArgumentNullException>( !string.IsNullOrEmpty( name ) );

			// get the image file
			IFile file = BlockTexturesFolder.GetFile( name + ".png" );
			if( file == null )
				return null;

			// get the image from the file
			Bitmap image = GetOrReadImage( file );
			if( image == null )
				return null;

			// now check if there is a txt file with frame information
			TextureMetadata metadata = null;
			IFile metaFile = file.GetActualParentFolder().GetFile( name + ".png.mcmeta" );
			if( metaFile != null )
				metadata = Metadata.Metadata.Read<TextureMetadata>( metaFile );

			// load the texture from memory stream
			return new Texture( image, metadata );
		}
		
		/// <summary>
		/// Readds the image from the given file in the pack.
		/// </summary>
		/// <param name="file">The file in the pack containing the image.</param>
		/// <returns>The bitmap containing the image of the file, or <c>null</c> when the file could not be found, or was not a bitmap.</returns>
		private Bitmap GetOrReadImage( IFile file )
		{
			// no file given, return null as image
			if( file == null )
				return null;

			// try to get from cache if one is given
			Bitmap image = null;
			string key = file.ToString();
			if( _imageCache != null )
				image = _imageCache.GetImage( key );

			// none in the cache, actual load it from file
			if( image == null )
			{
				using( Stream stream = file.OpenRead() )
				{
					image = Image.FromStream( stream ) as Bitmap;

					// found image and we are caching?, then add it to the cache
					if( image != null && _imageCache != null )
						_imageCache.AddImage( key, image );
				}
			}

			// return the possibly found image
			return image;
		}
	}
}
